﻿using System;
using System.Data.SqlClient;
using System.Linq;
using Fuse8.DomainFramework.BusinessService;
using Fuse8.DomainFramework.Common.Locator;
using Fuse8.DomainFramework.Practices.BusinessService.Extensions;
using Fuse8.DomainFramework.Practices.DataAccess;
using Fuse8.DomainFramework.Practices.DataAccess.Commands;
using Fuse8.DomainFramework.Practices.DataAccess.SqlRepository;
using Fuse8.DomainFramework.Practices.Domain;
using Fuse8.DomainFramework.Practices.Domain.Interfaces;
using Fuse8.DomainFramework.Practices.SystemFacade.Extensions;
using Fuse8.DomainFramework.UnityLocator;
using Microsoft.Practices.Unity;
using NUnit.Framework;
using UnitTests.Domain;

namespace UnitTests
{
	[TestFixture]
	public class SqlBaseMapperTests
	{
		[SetUp]
		public void Init()
		{
			var container = new UnityContainer();
			var locator = new UnityServiceLocatorWithLazy(container);
			DependencyServiceLocator.SetLocatorProvider(() => locator);

			container.RegisterType(typeof(IEntityDataCommand<,,>), typeof(SqlBaseCommand<,,>));
			container.RegisterInstance<IConnectionStringFactory>(new ConnectionStringFactory());

			container.RegisterSimpleSql<Article>();
			container.RegisterSimpleSql<ArticleTag>();

			container.RegisterSimpleSql<InfoContainer>(p=>p
			    .AddMultiProperties<GetFullInfoCommandParameter>(q => new
					{
						Users = q.MultiMap<IUser, InfoContainer>(d => d.Users, (s, d) => true),
						Files = q.MultiMap<File, InfoContainer>(d => d.Files, (s, d) => true),
					}
				));
		}

		[Test]
		public void MapperTest()
		{
			var article = new BusinessServiceFacade<Article, int>().FindOne(3);
			Assert.That(article.Title, Is.EqualTo("Homer Simpson as the best developer"));
		}

		[Test]
		public void MapperNestedEntityTest()
		{
			var articleTag = new BusinessServiceFacade<ArticleTag, int>().FindOne(x => x.Id == 1, x => new {x, x.Article, x.Article.File});
			Assert.That(articleTag.Article.File.Id, Is.EqualTo(1));
		}

		[Test]
		public void MapperOnCreatedTest()
		{
			var articleTag = new BusinessServiceFacade<ArticleTag, int>().FindOne(x => x.Id == 1, x => new { x, x.Article, x.Article.File });
			Assert.That(articleTag.OnCreatedCalls, Is.EqualTo(1));
		}

		[Test]
		public void MultiMapperTest()
		{
			var settings = new SqlMultiMapperSettings<Article> { FirstSetIsEntity = true };
			settings.AddProperties<GetByIdCommandParameter>(p => new
			{
				ArticleTags = p.MultiMap<ArticleTag, Article>(t => t.ArticleTags, q => q.Article, (q, t) => q.ArticleId == t.Id)
			}, 1);
			using (var sqlConnection = new SqlConnection(new ConnectionStringFactory().GetConnectionString<Article>()))
			{
				sqlConnection.Open();
				Article article = null;
				var mapper = new SqlBaseMapper<Article, int>(null, null, settings);

				var articleCommand = new SqlCommand("SELECT TOP 1 * FROM Articles", sqlConnection);
				var articleReader = articleCommand.ExecuteReader();
				while (articleReader.Read())
				{
					article = mapper.MapFromMulti("cusp_ArticleGetById", articleReader, article, 0);
				}
				articleReader.Close();
				var sqlCommand = new SqlCommand("SELECT * FROM ArticleTags", sqlConnection);
				var reader = sqlCommand.ExecuteReader();
				while (reader.Read())
				{
					mapper.MapFromMulti("cusp_ArticleGetById", reader, article, 1);
				}
				sqlConnection.Close();
				Assert.That(article.ArticleTags.Count(), Is.EqualTo(2));
			}
		}

		[Test]
		public void MultiMapper3Test()
		{
			var settings = new SqlMultiMapperSettings<ArticleTag> { FirstSetIsEntity = true };
			settings.AddProperties<GetByIdCommandParameter>(p => new
			{
				Article = p.MultiMap<Article, ArticleTag>(t => t.Article, (q, t) => true),
				ArticleTags = p.MultiMap<ArticleTag, Article>(t => t.ArticleTags, q => q.Article, (q, t) => q.Id == 2)
			}, 1);
			using (var sqlConnection = new SqlConnection(new ConnectionStringFactory().GetConnectionString<ArticleTag>()))
			{
				sqlConnection.Open();
				ArticleTag articleTag = null;
				var mapper = new SqlBaseMapper<ArticleTag, int>(null, null, settings);

				var articleTagCommand = new SqlCommand("SELECT TOP 1 * FROM ArticleTags WHERE Id = 1", sqlConnection);
				var articleTagReader = articleTagCommand.ExecuteReader();
				while (articleTagReader.Read())
				{
					articleTag = mapper.MapFromMulti("cusp_ArticleTagGetById", articleTagReader, articleTag, 0);
				}
				articleTagReader.Close();
				var sqlCommand = new SqlCommand("SELECT TOP 1 * FROM Articles", sqlConnection);
				var reader = sqlCommand.ExecuteReader();
				while (reader.Read())
				{
					mapper.MapFromMulti("cusp_ArticleTagGetById", reader, articleTag, 1);
				}
				reader.Close();
				var articleTag2Command = new SqlCommand("SELECT TOP 1 * FROM ArticleTags WHERE Id = 2", sqlConnection);
				var articleTag2Reader = articleTag2Command.ExecuteReader();
				while (articleTag2Reader.Read())
				{
					articleTag = mapper.MapFromMulti("cusp_ArticleTagGetById", articleTag2Reader, articleTag, 2);
				}
				articleTag2Reader.Close();
				sqlConnection.Close();

				Assert.That(articleTag.Article.ArticleTags.Count(), Is.EqualTo(1));
			}
		}

        [Test]
        public void MultiMapperMapToNestedEntityTest()
        {
            var settings = new SqlMultiMapperSettings<ArticleTag> { FirstSetIsEntity = true };
            settings.AddProperties<GetFullInfoCommandParameter>(p => new
            {
                ArticleTags = p.MultiMap<ArticleTag, ArticleTag>(t => t.Article.ArticleTags, (q, t) => q.Id == 2)
            }, 1);
            using (var sqlConnection = new SqlConnection(new ConnectionStringFactory().GetConnectionString<ArticleTag>()))
            {
                sqlConnection.Open();
                ArticleTag articleTag = null;
                var mapper = new SqlBaseMapper<ArticleTag, int>(null, null, settings);

                var articleTagCommand = new SqlCommand("SELECT TOP 1  t.*, a.Id Article_Id FROM ArticleTags t join Articles a on a.Id = t.ArticleId WHERE t.Id = 1", sqlConnection);
                var articleTagReader = articleTagCommand.ExecuteReader();
                while (articleTagReader.Read())
                {
                    articleTag = mapper.MapFromMulti("cusp_ArticleTagGetFullInfo", articleTagReader, articleTag, 0);
                }
                articleTagReader.Close();
                var articleTag2Command = new SqlCommand("SELECT TOP 1 * FROM ArticleTags WHERE Id = 2", sqlConnection);
                var articleTag2Reader = articleTag2Command.ExecuteReader();
                while (articleTag2Reader.Read())
                {
                    articleTag = mapper.MapFromMulti("cusp_ArticleTagGetFullInfo", articleTag2Reader, articleTag, 1);
                }
                articleTag2Reader.Close();
                sqlConnection.Close();

                Assert.That(articleTag.Article.ArticleTags.Count(), Is.EqualTo(1));
            }
        }


		[Test]
		public void MultiMapperContainerTest()
		{
			var model = new BusinessServiceFacade<InfoContainer, int>().FindOne(new GetFullInfoCommandParameter());
			Assert.That(model.Users.Count(), Is.EqualTo(4));
			Assert.That(model.Files.Count(), Is.EqualTo(10));
		}

		[Test]
		public void MultiMapperUpdatedFieldsTest()
		{
			var model = new BusinessServiceFacade<InfoContainer, int>().FindOne(new GetFullInfoCommandParameter());
			var user = model.Users.Last();
			var tracker = (IFieldTracker) user;
			CollectionAssert.IsEmpty(tracker.GetUpdatedFields().ToArray());
			user.Name = "test";
			CollectionAssert.AreEquivalent(new[] { "Name" }, tracker.GetUpdatedFields().ToArray());
		}

		[Test]
		public void MultiMapperNotLoadedFieldTest()
		{
			var article = new BusinessServiceFacade<Article, int>().FindOne(new GetFullInfoCommandParameter());
			var ex = Assert.Throws(Is.InstanceOf<PropertyNotLoadedException>(), () => { var a = article.File.OriginalName; });
			StringAssert.Contains("cusp_ArticleGetFullInfo", ex.Message);
		}

		[Test]
		public void EnumTest()
		{
			var settings = new SqlMultiMapperSettings<Article> { FirstSetIsEntity = true };
			using (var sqlConnection = new SqlConnection(new ConnectionStringFactory().GetConnectionString<Article>()))
			{
				sqlConnection.Open();
				Article article = null;
				var mapper = new SqlBaseMapper<Article, int>(null, null, settings);

                var articleCommand = new SqlCommand("SELECT 'test' as Title, 1 as Id, 2 as ArticleType, 1 as SuperType, null as SuperPuperType", sqlConnection);
				var articleReader = articleCommand.ExecuteReader();
				while (articleReader.Read())
				{
					article = mapper.MapFromMulti("cusp_ArticleEnumTest", articleReader, article, 0);
				}
				articleReader.Close();
				Assert.That(article.ArticleType, Is.EqualTo(ArticleType.Public));
                Assert.IsNull(article.SuperPuperType);
                Assert.AreEqual(ArticleType.Reserved, article.SuperType);
			}
		}

		[Test]
		public void PropertyTypeExceptionTest()
		{
			var settings = new SqlMultiMapperSettings<Article> { FirstSetIsEntity = true };
			using (var sqlConnection = new SqlConnection(new ConnectionStringFactory().GetConnectionString<Article>()))
			{
				sqlConnection.Open();
				Article article = null;
				var mapper = new SqlBaseMapper<Article, int>(null, null, settings);

				var articleCommand = new SqlCommand("SELECT 1 as Id, 1 as Title", sqlConnection);
				var articleReader = articleCommand.ExecuteReader();
				while (articleReader.Read())
				{
					var e = Assert.Throws<Exception>(
						() => { article = mapper.MapFromMulti("cusp_ArticlePropertyTypeExceptionTest", articleReader, article, 0); });
					Assert.That(e.Message, Is.EqualTo("Wrong property Article.Title type Int32, expected String. Please check Stored Procedure 'cusp_ArticlePropertyTypeExceptionTest_0'"));
				}
				articleReader.Close();
				
			}
		}

        [Test]
        public void PropertyTypeExceptionEnumTest()
        {
            var settings = new SqlMultiMapperSettings<Article> { FirstSetIsEntity = true };
            using (var sqlConnection = new SqlConnection(new ConnectionStringFactory().GetConnectionString<Article>()))
            {
                sqlConnection.Open();
                Article article = null;
                var mapper = new SqlBaseMapper<Article, int>(null, null, settings);

                var articleCommand = new SqlCommand("SELECT 1 as Id, 'None' as SuperType", sqlConnection);
                var articleReader = articleCommand.ExecuteReader();
                while (articleReader.Read())
                {
                    var e = Assert.Throws<Exception>(
                        () => { article = mapper.MapFromMulti("cusp_ArticlePropertyTypeExceptionTest", articleReader, article, 0); });
                    Assert.That(e.Message, Is.EqualTo("Wrong property Article.SuperType type String, expected Nullable`1. Please check Stored Procedure 'cusp_ArticlePropertyTypeExceptionTest_0'"));
                }
                articleReader.Close();

            }
        }

		[Test]
		public void NullableBoolTest()
		{
			var settings = new SqlMultiMapperSettings<Article> { FirstSetIsEntity = true };
			using (var sqlConnection = new SqlConnection(new ConnectionStringFactory().GetConnectionString<Article>()))
			{
				sqlConnection.Open();
				Article article = null;
				var mapper = new SqlBaseMapper<Article, int>(null, null, settings);

				var articleCommand = new SqlCommand("SELECT 1 as Id, cast(1 as bit) as NullableBoolProperty", sqlConnection);
				var articleReader = articleCommand.ExecuteReader();
				while (articleReader.Read())
				{
					article = mapper.MapFromMulti("cusp_ArticleNullableTest", articleReader, article, 0);
				}
				articleReader.Close();
				Assert.That(article.NullableBoolProperty, Is.EqualTo(true));
			}
		}

		[Test]
		public void MoneyTest()
		{
			var settings = new SqlMultiMapperSettings<Article> { FirstSetIsEntity = true };
			using (var sqlConnection = new SqlConnection(new ConnectionStringFactory().GetConnectionString<Article>()))
			{
				sqlConnection.Open();
				Article article = null;
				var mapper = new SqlBaseMapper<Article, int>(null, null, settings);

				var articleCommand = new SqlCommand("SELECT 1 as Id, cast(1 as money) as NullableDecimalProperty", sqlConnection);
				var articleReader = articleCommand.ExecuteReader();
				while (articleReader.Read())
				{
					article = mapper.MapFromMulti("cusp_ArticleNullableDecimalTest", articleReader, article, 0);
				}
				articleReader.Close();
				Assert.That(article.NullableDecimalProperty, Is.EqualTo(1));
			}
		}

		[Test]
		public void BinaryTest()
		{
			var settings = new SqlMultiMapperSettings<Article> { FirstSetIsEntity = true };
			using (var sqlConnection = new SqlConnection(new ConnectionStringFactory().GetConnectionString<Article>()))
			{
				sqlConnection.Open();
				Article article = null;
				var mapper = new SqlBaseMapper<Article, int>(null, null, settings);

				var articleCommand = new SqlCommand("SELECT 1 as Id, convert(varbinary(max),'123') as BinaryProperty", sqlConnection);
				var articleReader = articleCommand.ExecuteReader();
				while (articleReader.Read())
				{
					article = mapper.MapFromMulti("cusp_ArticleBinaryTest", articleReader, article, 0);
				}
				articleReader.Close();
				CollectionAssert.AreEqual(new[] {0x31, 0x32, 0x33}, article.BinaryProperty);
			}
		}
	}
}